#include "StorageProtocol.h"
#include "Packets.h"
#include <boost/filesystem.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/system/error_code.hpp>

#define REGISTER(PACKET, HANDLER) AddHandler<StorageProtocol> ((int32_t) CSNetworkLib::PACKET, & StorageProtocol::HANDLER, false)
#define REGISTER_ASYNC(PACKET, HANDLER) AddHandler<StorageProtocol> ((int32_t) CSNetworkLib::PACKET, & StorageProtocol::HANDLER, true)
#define ERROR(x) ((x)!=boost::system::errc::success)

inline std::string TrimString(const std::string &str)
{
	std::string tmp = str;

	while((!tmp.empty()) && ((char)(' ')==tmp[0]))
	{
		tmp.erase(tmp.begin());
	}

	while((!tmp.empty()) && ((char)(' ')==tmp[tmp.size()-1]))
	{
		tmp.erase(tmp.end()-1);
	}
	return tmp;
}

bool IsPathCorrect(boost::filesystem::path &in)
{
	if (!in.is_absolute())
		return false;
	boost::system::error_code e1;
	std::string tmp = boost::filesystem::system_complete(in.make_preferred(), e1).string();
	if (ERROR(e1))
		return false;
	if (tmp!=in.string())
		return false;
	if (tmp.empty())
		return false;
	return true;
}

StorageProtocol::StorageProtocol(socket_ptr s) : CSNetworkLib::NetworkProtocol(s, 10000)
{
	REGISTER(PACKET_STORAGE_OnGetType_Req,OnGetType);
	REGISTER(PACKET_STORAGE_OnFileOpen_Req,OnFileOpen);
	REGISTER(PACKET_STORAGE_OnFileClose_Req,OnFileClose);
	REGISTER(PACKET_STORAGE_OnFileInfo_Req,OnFileInfo);
	REGISTER(PACKET_STORAGE_OnFileSeek_Req,OnFileSeek);
	REGISTER(PACKET_STORAGE_OnFileWrite_Req,OnFileWrite);
	REGISTER(PACKET_STORAGE_OnFileRead_Req,OnFileRead);
    REGISTER(PACKET_STORAGE_OnFileEof_Req,OnFileEof);

	REGISTER_ASYNC(PACKET_STORAGE_OnFileDelete_Req,OnFileDelete);
	REGISTER_ASYNC(PACKET_STORAGE_OnFileMove_Req,OnFileMove);
	REGISTER_ASYNC(PACKET_STORAGE_OnFileCopy_Req,OnFileCopy);
	REGISTER_ASYNC(PACKET_STORAGE_OnFolderSync_Req,OnFolderSync);
	REGISTER(PACKET_STORAGE_OnFolderCreate_Req,OnFolderCreate);
	REGISTER_ASYNC(PACKET_STORAGE_OnFolderMove_Req,OnFolderMove);
	REGISTER_ASYNC(PACKET_STORAGE_OnFolderCopy_Req,OnFolderCopy);
	REGISTER_ASYNC(PACKET_STORAGE_OnFolderDelete_Req,OnFolderDelete);
}

StorageProtocol::~StorageProtocol()
{
	while(!m_openFiles.empty())
	{
		fclose(m_openFiles.begin()->first);
		m_openFiles.erase(m_openFiles.begin());
	}
}

bool StorageProtocol::Auth(const std::string &name, const std::string &key)
{
	FUNCTION_TRACK
    CSNetworkLib::NetworkPacket request(CSNetworkLib::PACKET_SERVER_AuthStorageReq);
    CSNetworkLib::NetworkPacket response;

    request.Push_String(name.c_str());
    request.Push_String(key.c_str());

    int32_t storageId = 0;
    if (SendPacketWait(request, response, 5000)) {
        response.Get_S32(0, storageId);
    }
    return storageId>0;
}

bool StorageProtocol::SendFileInfo(	const boost::filesystem::path &link, bool force)
{
	FUNCTION_TRACK
	CSNetworkLib::NetworkPacket msg(CSNetworkLib::PACKET_SERVER_OnSyncFileInfo);

	boost::system::error_code e1, e2;
	boost::filesystem::path file(link);

	msg.Push_String(file.string().c_str());
	if (boost::filesystem::exists(file, e1) && !boost::filesystem::is_directory(file, e2) && (!ERROR(e1)) && (!ERROR(e2)))
	{
		msg.Push_String(file.parent_path().string().c_str());
		msg.Push_String(file.filename().string().c_str());
		msg.Push_S64(boost::filesystem::file_size(file, e1));
		msg.Push_U64(boost::filesystem::last_write_time(file, e1));
		INFO("SendFileInfo(SUCCESS) - %s", file.string().c_str());
	}
	else
	{
		INFO("SendFileInfo(FAILED) - %s", file.string().c_str());
	}
	return SendPacket(msg);
}

bool StorageProtocol::SendFolderInfo( const boost::filesystem::path &link, bool sync, bool force)
{
	FUNCTION_TRACK
	CSNetworkLib::NetworkPacket msg(CSNetworkLib::PACKET_SERVER_OnSyncFolderInfo);

	boost::system::error_code e1, e2;
	boost::filesystem::path dir(link);
	msg.Push_String(dir.string().c_str());
	if (boost::filesystem::exists(dir, e1) && boost::filesystem::is_directory(dir, e2) && (e1 == boost::system::errc::success)&& (e2 == boost::system::errc::success))
	{
		msg.Push_String(dir.parent_path().string().c_str());
		msg.Push_String(dir.filename().string().c_str());
		msg.Push_Bool(sync);
		INFO("SendFolderInfo(SUCCESS) - %s", dir.string().c_str());
	}
	else
	{
		INFO("SendFolderInfo(FAILED) - %s", dir.string().c_str());
	}
	return SendPacket(msg);
}

void StorageProtocol::OnGetType(const CSNetworkLib::NetworkPacket &request)
{
	FUNCTION_TRACK
	CSNetworkLib::NetworkPacket response(request, CSNetworkLib::PACKET_STORAGE_OnGetType_Resp);
	bool extended = false;
	request.Get_Bool(0, extended);
	if (2 == request.GetCount())
	{
		boost::filesystem::path dir(request.GetString(1));
		if (!IsPathCorrect(dir))
		{
			ERR("Path: %s is not absolute!", dir.string().c_str());
			response.Push_U8(0);
			SendPacket(response);
			return;
		}
		boost::system::error_code error;
		boost::filesystem::file_status fs = boost::filesystem::status(dir, error);

		if (fs.type() == boost::filesystem::regular_file)
		{
			SendFileInfo(dir);
			response.Push_U8(2);
			if (extended)
			{
				response.Push_String(dir.c_str());
				response.Push_String(dir.filename().c_str());
			}
			SendPacket(response);
			return;
		}
		if (fs.type() == boost::filesystem::directory_file)
		{
			SendFolderInfo(dir, false);
			response.Push_U8(3);
			if (extended)
			{
				response.Push_String(dir.c_str());
				response.Push_String(dir.filename().c_str());
			}
			SendPacket(response);
			return;
		}
		if (fs.type() == boost::filesystem::status_error)
		{
			response.Push_U8(0);
			SendPacket(response);
			return;
		}
		if (fs.type() == boost::filesystem::symlink_file)
		{
			SendFileInfo(dir);
			response.Push_U8(2);
			if (extended)
			{
				response.Push_String(dir.c_str());
				response.Push_String(dir.filename().c_str());
			}
			SendPacket(response);
			return;
		}

		response.Push_U8(1);
		if (extended)
		{
			response.Push_String(dir.c_str());
			response.Push_String(dir.filename().c_str());
		}
		SendPacket(response);
		return;
	}
	if (3 == request.GetCount())
	{
		boost::filesystem::path dir(request.GetString(1));
		dir /= boost::filesystem::path(request.GetString(2));
		if (!IsPathCorrect(dir))
		{
			ERR("Path: %s is not absolute!", dir.string().c_str());
			response.Push_U8(0);
			SendPacket(response);
			return;
		}
		boost::system::error_code error;
		boost::filesystem::file_status fs = boost::filesystem::status(dir, error);

		if (fs.type() == boost::filesystem::regular_file)
		{
			SendFileInfo(dir);
			response.Push_U8(2);
			if (extended)
			{
				response.Push_String(dir.c_str());
				response.Push_String(dir.filename().c_str());
			}
			SendPacket(response);
			return;
		}
		if (fs.type() == boost::filesystem::directory_file)
		{
			SendFolderInfo(dir, false);
			response.Push_U8(3);
			if (extended)
			{
				response.Push_String(dir.c_str());
				response.Push_String(dir.filename().c_str());
			}
			SendPacket(response);
			return;
		}
		if (fs.type() == boost::filesystem::status_error)
		{
			response.Push_U8(0);
			SendPacket(response);
			return;
		}
		if (fs.type() == boost::filesystem::symlink_file)
		{
			SendFileInfo(dir);
			response.Push_U8(2);
			if (extended)
			{
				response.Push_String(dir.c_str());
				response.Push_String(dir.filename().c_str());
			}
			SendPacket(response);
			return;
		}

		response.Push_U8(1);
		if (extended)
		{
			response.Push_String(dir.c_str());
			response.Push_String(dir.filename().c_str());
		}
		SendPacket(response);
		return;
	}
	response.Push_U8(0);
	SendPacket(response);
	return;
}

void StorageProtocol::OnFileOpen(const CSNetworkLib::NetworkPacket &request)
{
	FUNCTION_TRACK
	CSNetworkLib::NetworkPacket response(request, CSNetworkLib::PACKET_STORAGE_OnFileOpen_Resp);
	boost::filesystem::path name(request.GetString(0));
	if (!IsPathCorrect(name))
	{
		ERR("Path: %s is not absolute!", name.string().c_str());
		response.Push_U32(0);
		SendPacket(response);
		return;
	}
	std::string options = request.GetString(1);
	boost::filesystem::path tmp(request.GetString(2));
	if (!tmp.empty())
	{
		name /= tmp;
	}

	INFO("Opening file %s options %s", name.string().c_str(), options.c_str());

	FILE *f = fopen(name.string().c_str(), options.c_str());
	SendFileInfo(name);
	if (NULL == f)
	{
		response.Push_U32(0);
		ERR("Open file: Failed");
	}
	else
	{
		INFO("Open file: Success");
		response.Push_U32((uint32_t)f);
		m_openFiles[f] = name.string();
	}
	SendPacket(response);
}

void StorageProtocol::OnFileClose(const CSNetworkLib::NetworkPacket &request)
{
	FUNCTION_TRACK
	CSNetworkLib::NetworkPacket response(request, CSNetworkLib::PACKET_STORAGE_OnFileClose_Resp);
	uint32_t f = 0;
	request.Get_U32(0, f);
	FILE *f2 = (FILE*)f;

	std::map<FILE*, std::string>::iterator i = m_openFiles.find(f2);
	if (i!=m_openFiles.end())
	{
		if (0 == fclose(f2))
		{
			SendFileInfo(i->second);
			m_openFiles.erase(i);
			response.Push_U32(0);
		}
		else
			response.Push_U32(2);

	}
	else
	{
		response.Push_U32(1);
	}
	SendPacket(response);
}

bool StorageProtocol::IsOpenFile(FILE* handle)
{
	std::map<FILE*, std::string>::iterator i = m_openFiles.find(handle);
	return (handle !=NULL) && (i!=m_openFiles.end());
}

void StorageProtocol::OnFileInfo(const CSNetworkLib::NetworkPacket &request)
{
	FUNCTION_TRACK
	CSNetworkLib::NetworkPacket response(request, CSNetworkLib::PACKET_STORAGE_OnFileInfo_Resp);
	FILE* f = NULL;
	{
		uint32_t ft = 0;
		request.Get_U32(0, ft);
		f = (FILE*)ft;
		if (!IsOpenFile(f))
		{
				SendPacket(response);
				return;
		}
	}
    uint8_t id = 0;
    request.Get_U8(1, id);

    if (id == 2)
    {
        long int pos = ftell(f);
        if (-1 == pos)
        {
                SendPacket(response);
                return;
        }
        response.Push_U64(pos);
        SendPacket(response);
        return;
    }

    if (id == 1)
    {
        std::time_t t = 0;
        try{
            boost::filesystem::path p(m_openFiles[f]);
            t = boost::filesystem::last_write_time(p);
        }catch(...)
        {
        }
        response.Push_U64(t);
        SendPacket(response);
        return;
    }

    if (id == 0)
    {
        long int pos = ftell(f);
        if (-1 == pos)
        {
                SendPacket(response);
                return;
        }
        fseek(f, 0L, SEEK_END);
        long int size = ftell(f);
        fseek(f, pos, SEEK_SET);
        if (-1 == size)
        {
                SendPacket(response);
                return;
        }
        response.Push_U64(size);
        SendPacket(response);
        return;
    }

	SendPacket(response);
}

void StorageProtocol::OnFileSeek(const CSNetworkLib::NetworkPacket &request)
{
	FUNCTION_TRACK
	CSNetworkLib::NetworkPacket response(request, CSNetworkLib::PACKET_STORAGE_OnFileSeek_Resp);
	FILE* f = NULL;
	{
		uint32_t ft = 0;
		request.Get_U32(0, ft);
		f = (FILE*)ft;
		if (!IsOpenFile(f))
		{
			response.Push_Bool(false);
			SendPacket(response);
			return;
		}
	}
	int64_t p = -1;
	request.Get_S64(1, p);
	if (p < 0 )
	{
		response.Push_Bool(fseek(f, p+1, SEEK_END) == 0);
	} else
	{
		response.Push_Bool(fseek(f, p, SEEK_SET) == 0);
	}
	SendPacket(response);
}

void StorageProtocol::OnFileEof(const CSNetworkLib::NetworkPacket &request)
{
	FUNCTION_TRACK
	CSNetworkLib::NetworkPacket response(request, CSNetworkLib::PACKET_STORAGE_OnFileEof_Resp);
	FILE* f = NULL;
	{
		uint32_t ft = 0;
		request.Get_U32(0, ft);
		f = (FILE*)ft;
		if (!IsOpenFile(f))
		{
			SendPacket(response);
			return;
		}
	}
	response.Push_Bool(feof(f)!=0);
	SendPacket(response);
}


void StorageProtocol::OnFileWrite(const CSNetworkLib::NetworkPacket &request)
{
	FUNCTION_TRACK
	CSNetworkLib::NetworkPacket response(request, CSNetworkLib::PACKET_STORAGE_OnFileWrite_Resp);
	FILE* f = NULL;
	{
		uint32_t ft = 0;
		request.Get_U32(0, ft);
		f = (FILE*)ft;
		if (!IsOpenFile(f))
		{
				response.Push_S64(-1);
				SendPacket(response);
				return;
		}
	}
	uint32_t s = request.GetItemSize(1);
	size_t so = fwrite(request.Get_DataPtr(1), 1, s, f);
	response.Push_S64(so);
	SendPacket(response);
}

void StorageProtocol::OnFileRead(const CSNetworkLib::NetworkPacket &request)
{
	FUNCTION_TRACK
	CSNetworkLib::NetworkPacket response(request, CSNetworkLib::PACKET_STORAGE_OnFileRead_Resp);
	FILE* f = NULL;
	{
		uint32_t ft = 0;
		request.Get_U32(0, ft);
		f = (FILE*)ft;
		if (!IsOpenFile(f))
		{
				response.Push_S32(-1);
				SendPacket(response);
				return;
		}
	}
	uint32_t s = 0;
	request.Get_U32(1, s);
	if (s>0)
	{
		uint8_t *ptr = new uint8_t[s];
		s = fread(ptr, sizeof(uint8_t), s, f);
		response.Push_S32(s);
		response.Push_Data(ptr, s);
		delete []ptr;
	}
	response.Push_S32(0);
	SendPacket(response);
}

void StorageProtocol::OnFileDelete(const CSNetworkLib::NetworkPacket &request)
{
	FUNCTION_TRACK
	CSNetworkLib::NetworkPacket response(request, CSNetworkLib::PACKET_STORAGE_OnFileDelete_Resp);
	boost::filesystem::path p(request.GetString(0));
	if (!IsPathCorrect(p))
	{
		ERR("Path: %s is not absolute!", p.string().c_str());
		response.Push_S16(-1);
		SendPacket(response);
		return;
	}
	try{
		if (boost::filesystem::exists(p))
		{
			if (!boost::filesystem::is_regular_file(p))
			{
				SendFileInfo(p);
				response.Push_S16(-1);
				SendPacket(response);
				return;
			}
			boost::filesystem::remove(p);
		}
	} catch(...)
	{
		SendFileInfo(p);
		response.Push_S16(-1);
		SendPacket(response);
		return;
	}
	SendFileInfo(p);
	response.Push_S16(0);
	SendPacket(response);
}

void StorageProtocol::OnFileMove(const CSNetworkLib::NetworkPacket &request)
{
	FUNCTION_TRACK
	CSNetworkLib::NetworkPacket response(request, CSNetworkLib::PACKET_STORAGE_OnFileMove_Resp);
	try {
		boost::filesystem::path file(request.GetString(0));
		boost::filesystem::path dir(request.GetString(1));
		boost::filesystem::path name(request.GetString(2));
		if (!IsPathCorrect(dir))
		{
			ERR("Path: %s is not absolute!", dir.string().c_str());
			response.Push_S16(-1);
			SendPacket(response);
			return;
		}
		bool force = false;
		request.Get_Bool(3, force);

		if (!boost::filesystem::exists(file))
		{
			SendFileInfo(file);
			response.Push_S16(-1);
			SendPacket(response);
			return;
		}
		if (!dir.empty())
		{
			boost::filesystem::create_directories(dir);
		}
		else
		{
			dir = file.parent_path();
		}

		if (!name.empty())
		{
			if (boost::filesystem::exists(dir/name))
			{
				if (!force)
				{
					response.Push_S16(-2);
					SendPacket(response);
					return;
				}
				if (boost::filesystem::is_directory(dir/name))
				{
					response.Push_S16(-1);
					SendPacket(response);
					return;
				}
			}
		}
		else
		{
			name = file.filename();
		}

		boost::filesystem::rename(file, dir/name);
		SendFileInfo(file);
		SendFileInfo(dir/name);
	} catch(...)
	{
		response.Push_S16(-1);
		SendPacket(response);
		return;
	}
	response.Push_S16(0);
	SendPacket(response);
	return;
}

void StorageProtocol::OnFileCopy(const CSNetworkLib::NetworkPacket &request)
{
	FUNCTION_TRACK
	CSNetworkLib::NetworkPacket response(request, CSNetworkLib::PACKET_STORAGE_OnFileCopy_Resp);
	try{
		boost::filesystem::path file(request.GetString(0));
		boost::filesystem::path dir(request.GetString(1));
		boost::filesystem::path name(request.GetString(2));
		if (!IsPathCorrect(dir))
		{
			ERR("Path: %s is not absolute!", dir.string().c_str());
			response.Push_S16(-1);
			SendPacket(response);
			return;
		}
		bool force = false;
		request.Get_Bool(3, force);

		if (!boost::filesystem::exists(file))
		{
			SendFileInfo(file);
			response.Push_S16(-1);
			SendPacket(response);
			return;
		}
		if (!dir.empty())
		{
			boost::filesystem::create_directories(dir);
		}
		else
		{
			dir = file.parent_path();
		}

		if (!name.empty())
		{
			if (boost::filesystem::exists(dir/name))
			{
				if (!force)
				{
					response.Push_S16(-2);
					SendPacket(response);
					return;
				}
				if (boost::filesystem::is_directory(dir/name))
				{
					response.Push_S16(-1);
					SendPacket(response);
					return;
				}
			}
		}
		else
		{
			name = file.filename();
		}

		boost::filesystem::copy_file(file, dir/name);
		SendFileInfo(file);
		SendFileInfo(dir/name);
	} catch(...)
	{
		response.Push_S16(-1);
		SendPacket(response);
		return;
	}
	response.Push_S16(0);
	SendPacket(response);
	return;
}

void StorageProtocol::OnFolderSync(const CSNetworkLib::NetworkPacket &request)
{
	FUNCTION_TRACK
	CSNetworkLib::NetworkPacket response(request, CSNetworkLib::PACKET_STORAGE_OnFolderSync_Resp);
	boost::filesystem::path link(request.GetString(0));
	if (!IsPathCorrect(link))
	{
		ERR("Path: %s is not absolute!", link.string().c_str());
		response.Push_U8(0);
		SendPacket(response);
		return;
	}
    boost::system::error_code e;
    if (!boost::filesystem::exists(link, e))
    {
		response.Push_U8(ERROR(e)?0:1);
		SendPacket(response);
		return;
    }

    if (!boost::filesystem::is_directory(link, e))
	{
		response.Push_U8(ERROR(e)?0:2);
		SendPacket(response);
		return;
    }

	bool recursive = false;
	request.Get_Bool(1, recursive);
	SendFolderInfo(link, true, true);

	try{
        if (recursive)
        {
            for ( boost::filesystem::recursive_directory_iterator end, dir(link);  dir != end; ++dir )
            {
                if (boost::filesystem::is_directory(*dir, e) && (e == boost::system::errc::success))
                    SendFolderInfo(*dir, true, true);
                else
                    SendFileInfo(*dir, true);
            }
        }
        else
        {
            for ( boost::filesystem::directory_iterator end, dir(link);  dir != end; ++dir )
            {
                if (boost::filesystem::is_directory(*dir, e) && (e == boost::system::errc::success))
                    SendFolderInfo(*dir, false, true);
                else
                    SendFileInfo(*dir, true);
            }
        }
	} catch(...) {}

    response.Push_U8(3);
	SendPacket(response);
}

void StorageProtocol::OnFolderCreate(const CSNetworkLib::NetworkPacket &request)
{
	FUNCTION_TRACK
	CSNetworkLib::NetworkPacket response(request, CSNetworkLib::PACKET_STORAGE_OnFolderCreate_Resp);
	boost::filesystem::path link(request.GetString(0));
	if (!IsPathCorrect(link))
	{
		ERR("Path: %s is not absolute!", link.string().c_str());
		response.Push_Bool(false);
		SendPacket(response);
		return;
	}
	boost::system::error_code e;

	INFO("Creating folder %s", link.string().c_str());

	if (!boost::filesystem::exists(link, e) || ERROR(e))
	{
        ERR("Folder exists %s", link.string().c_str());
		SendFolderInfo(link, false, true);
		response.Push_Bool(false);
		SendPacket(response);
		return;
	}

	std::string nname(request.GetString(1));
	if (nname.empty() ||
		nname.find("..")!=std::string::npos ||
		nname.find('?')!=std::string::npos ||
		nname.find('*')!=std::string::npos ||
		nname.find('"')!=std::string::npos ||
		nname.find('|')!=std::string::npos ||
		nname.find('<')!=std::string::npos ||
		nname.find('>')!=std::string::npos
		)
	{
        ERR("Folder name is invalid %s", link.string().c_str());
		response.Push_Bool(false);
		SendPacket(response);
		return;
	}
	boost::filesystem::path name(nname);

	boost::filesystem::path::iterator it = name.begin();
	while (it != name.end())
    {
		nname = TrimString((*it).string());
		if (nname.empty() || (nname == "."))
		{
			response.Push_Bool(false);
			SendPacket(response);
			return;
		}
		++it;
	}

	it = name.begin();
	while (it != name.end())
    {
		nname = TrimString((*it).string());
		++it;
		if ((nname == "/") || (nname == "\\"))
		{
			continue;
		}
		link /= boost::filesystem::path(nname);
		if (!boost::filesystem::exists(link, e) || ERROR(e))
		{
			if (!boost::filesystem::create_directory(link, e) || ERROR(e))
			{
				response.Push_Bool(false);
				SendPacket(response);
				return;
			}
			else
			{
				SendFolderInfo(link, true, false);
			}
		}
		else
		{
			SendFolderInfo(link, false, true);
		}
	}
	response.Push_Bool(true);
	SendPacket(response);
}

void StorageProtocol::OnFolderMove(const CSNetworkLib::NetworkPacket &request)
{
	FUNCTION_TRACK
	//przenosimy folder

}

void StorageProtocol::OnFolderCopy(const CSNetworkLib::NetworkPacket &request)
{
	FUNCTION_TRACK
}

void StorageProtocol::OnFolderDelete(const CSNetworkLib::NetworkPacket &request)
{
	FUNCTION_TRACK
}


